/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 */

import cluster, { Worker } from 'cluster';
import process from 'process';
import { Socket } from 'socket.io';
import util from 'util';

import { BuildEvent, WatchEvent } from '../../common/daemon-protocol/build-event.js';
import { BuildStatus } from '../../common/daemon-protocol/common-enum.js';
import { CancelBuildCommand, createBuildStatus, StopDaemonCommand } from '../../common/daemon-protocol/message.js';
import { coreParameter } from '../../internal/data/global-core-parameters.js';
import { initStartData } from '../../internal/data/global-data.js';
import { HVIGOR_PROCESS_EVENT_ID } from '../../internal/lifecycle/event/event-id-options.js';
import { evaluateLogLevel } from '../../log/hvigor-log.js';
import { HvigorCliOptions } from '../../util/options/hvigor-cli-options.js';
import { formatTime } from '../../util/time-util.js';
import { WORKER_PROCESS_EVENT_ID } from '../cluster/worker-process-event-id.js';
import { HvigorDaemonLogger } from '../log/hvigor-daemon-log.js';
import { getCurrentProcessState, resetProcessTimeOutTimer, setCurrentProcessState } from '../process/daemon-process-lifecycle.js';
import { DaemonState } from '../registry/daemon-info.js';
import { DaemonServerEventName, HvigorDaemonServer } from '../server/hvigor-daemon-server.js';
import { defaultSessionManager } from '../session/session-manager.js';

import { handleClientMsgError } from './daemon-server-socket-handler.js';
import { checkHvigorConfigBeforeProgram } from '../../util/check-hvigor-config-before-program.js';

const _log = HvigorDaemonLogger.getLogger('daemon');

/**
 * daemon侧的socket上所需要添加的事件监听
 */
export class DaemonServerSocketListenerRegistry {
  private readonly socket: Socket;

  constructor(socket: Socket) {
    this.socket = socket;
  }

  /**
   * 注册所有socket都需要注册的事件监听
   */
  registryBaseListener(hvigorDaemonServer: HvigorDaemonServer) {
    this.onStartBuild(hvigorDaemonServer);
    this.onCancel();
    this.onStop();
    this.onError();
    this.onDisconnected(hvigorDaemonServer);
  }

  /**
   * 注册收到开始构建任务的消息的事件监听
   * @private
   */
  private onStartBuild(hvigorDaemonServer: HvigorDaemonServer) {
    this.socket.on(BuildEvent.COMMON_BUILD, async (data) => {
      if (!data) {
        const reason = 'hvigor daemon: Received client exception type information. Empty build arguments.';
        this.socket.emit(BuildEvent.BUILD_STATUS, createBuildStatus(BuildStatus.REJECT, -1, reason));
        return;
      }

      const beginTime = process.hrtime();
      const cliArgv = data as HvigorCliOptions;

      // 如果编译先成功了一次，后续启动不会走入口而是直接从这里启动build
      const hvigorConfigValidateResult = checkHvigorConfigBeforeProgram(cliArgv, _log);
      if (!hvigorConfigValidateResult.result) {
        this.socket.emit(
          BuildEvent.BUILD_STATUS,
          createBuildStatus(BuildStatus.REJECT, -1, hvigorConfigValidateResult.stack ?? hvigorConfigValidateResult.message),
        );
      }

      try {
        initStartData(cliArgv);
      } catch (e: any) {
        _log.error(e);
        this.socket.emit(BuildEvent.BUILD_STATUS, createBuildStatus(BuildStatus.REJECT, -1, e.stack ?? e.message));
        return;
      }
      evaluateLogLevel(coreParameter.startParams.logLevel, ['daemon']);

      // 如果每次构建状态由下一次构建时刷新、因为watch场景不触发onCancel，需要允许存在activeSocket时允许halfBusy状态
      if (
        (defaultSessionManager.getActiveSocket() && getCurrentProcessState() !== DaemonState.HALF_BUSY) ||
        getCurrentProcessState() === DaemonState.STOP_REQ ||
        getCurrentProcessState() === DaemonState.BUSY
      ) {
        const reason = 'hvigor daemon: Current process status is busy, cannot start a build action. ' + 'Please wait a moment and retry.';
        _log.error(reason);
        this.socket.emit(BuildEvent.BUILD_STATUS, createBuildStatus(BuildStatus.REJECT, 0, reason));
        return;
      }

      // 在这里开始要设置curActiveSocket，并重定向输出流
      defaultSessionManager.setActiveSocket(this.socket);

      // 当有请求接受时,需要将process状态从HALF_BUSY状态转变为STOP_REQUEST状态,理论上执行到此处只可能为HALF_BUSY状态
      if (getCurrentProcessState() === DaemonState.HALF_BUSY) {
        setCurrentProcessState(DaemonState.STOP_REQ);
      }

      _log.debug(`hvigor daemon: Receive data from client. data=${util.format(data)}.`);
      _log.debug('hvigor daemon: Build start.');

      // 当开始执行请求时,需要将process状态从STOP_REQ状态转变为BUSY状态
      if (getCurrentProcessState() === DaemonState.STOP_REQ) {
        setCurrentProcessState(DaemonState.BUSY);
      }

      const { exitCode, reason } = await this.awaitBuildResult(cliArgv);

      // 构建流程执行结束后,不论是成功还是失败,需要重新设置process的timeout interval
      resetProcessTimeOutTimer(hvigorDaemonServer);

      // 当构建结束后,需要将process状态从BUSY状态转变为HALF_BUSY状态
      if (getCurrentProcessState() === DaemonState.BUSY) {
        setCurrentProcessState(DaemonState.HALF_BUSY);
      }

      _log.debug(`hvigor daemon: Build end. endTime=${formatTime(process.hrtime(beginTime))}`);
      this.socket.emit(BuildEvent.BUILD_STATUS, createBuildStatus(BuildStatus.FINISH, exitCode, reason));
    });
  }

  private async awaitBuildResult(cliArgv: HvigorCliOptions) {
    async function getWorker(): Promise<Worker> {
      let worker = Object.values(cluster.workers!)[0];
      if (!worker || !worker.isConnected() || worker.isDead()) {
        _log.debug('hvigor daemon: no worker before build. Build after starting a new worker process.');
        worker = cluster.fork();
        worker.process.stdout!.pipe(process.stdout);
        worker.process.stderr!.pipe(process.stderr);
        await new Promise<void>((resolve) => {
          // td: 补充online超时，提取公共方法
          worker?.once('online', resolve);
        });
      }
      return worker as Worker;
    }
    let [exitCode, reason] = [0, undefined];
    if (cluster.isMaster) {
      const worker = await getWorker();

      await new Promise<void>((resolve) => {
        // 目前只有一个worker，等以后一个daemon支持多worker的时候再调整
        defaultSessionManager.bindProcess(this.socket.id, worker.id);

        // 等待worker进程返回信息，或者worker进程在构建等待中退出，任一触发即可，并取消其他监听
        // 理论上支持多worker应该也可以在这里await，不会阻塞进程
        const workerProcessCallback = (message: any) => {
          if (message.type) {
            if (message.type === WORKER_PROCESS_EVENT_ID.FINISHED) {
              exitCode = 0;
              _log.debug(`hvigor daemon: worker process ${worker.process.pid} finished. Build Finished.`);
            } else if (message.type === WORKER_PROCESS_EVENT_ID.FAILED) {
              exitCode = -1;
              _log.debug(`hvigor daemon: worker process ${worker.process.pid} finished. Build Failed.`);
              reason = message.content;
            } else {
              // 因为监听的是message事件，要精准控制在message type为这两个字段
              return;
            }
            worker.off('exit', workerProcessExitCallback);
            worker.off('message', workerProcessCallback);
            resolve();
          }
        };
        const workerProcessExitCallback = (code: number, signal: string) => {
          _log.debug(`hvigor daemon: worker process ${worker.process.pid} exit (${code || signal}). Build Failed.`);
          exitCode = code;
          worker.off('message', workerProcessCallback);
          resolve();
        };

        worker.on('message', workerProcessCallback);
        worker.once('exit', workerProcessExitCallback);

        // 构建，启动！
        worker.send({
          type: BuildEvent.COMMON_BUILD,
          content: cliArgv,
        });
      });
    }
    return { exitCode, reason };
  }

  /**
   * 注册收到取消构建指令的消息的事件监听
   * @private
   */
  private onCancel() {
    this.socket.on(BuildEvent.CANCEL_BUILD, (data) => {
      if (!data) {
        handleClientMsgError(_log, data);
        return;
      }

      const command: CancelBuildCommand = data as CancelBuildCommand;

      _log.info(util.format(command));
      this.stopTask();
      this.socket.emit(BuildStatus.CLOSE, createBuildStatus(BuildStatus.CLOSE, 0));

      // 移除curActiveSocket
      defaultSessionManager.resetActiveSocket();
    });
  }

  /**
   * 注册收到关闭daemon进程的事件监听
   * @private
   */
  private onStop() {
    this.socket.on(BuildEvent.STOP_DAEMON, (data) => {
      if (!data) {
        handleClientMsgError(_log, data);
        return;
      }

      const command: StopDaemonCommand = data as StopDaemonCommand;

      _log.info(util.format(command));
      process.exit();
    });
  }

  /**
   * 注册当socket连接发生错误时触发的事件监听
   * @private
   */
  private onError() {
    this.socket.on('error', (error) => {
      _log.error('error', error);
      this.stopTask();
    });
  }

  private onDisconnected(hvigorDaemonServer: HvigorDaemonServer) {
    this.socket.on('disconnect', (reason) => {
      // 考虑socket自身的重连接机制
      _log.debug('hvigor disconnect: ', reason);
      this.stopTask();
      hvigorDaemonServer.emit(DaemonServerEventName.CLOSE_CONNECTION, this.socket);
    });
  }

  /**
   * 在server端，socket需要根据自己绑定线程状态来停止相应的任务：
   * 1. curActiveSocket: 终止工作进程的hvigor任务执行，同时要让主进程的事件循环不阻塞。
   * 2. threadId != undefined: 向相应worker线程发送终止信号。
   * 3. threadId == undefined: 无需处理。
   * @private
   */
  private stopTask() {
    if (defaultSessionManager.getActiveSocket()?.id === this.socket.id) {
      defaultSessionManager.postMessageToThread(this.socket.id, {
        type: HVIGOR_PROCESS_EVENT_ID.FAILED,
        reason: 'Manually stop by user.',
      });
    }
    defaultSessionManager.postMessageToThread(this.socket.id, {
      type: WatchEvent.CLOSE_WATCH,
    });
  }
}
